package org.filesort.helpers;

import java.io.*;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;


import static com.google.common.base.Preconditions.checkArgument;

/**
 * @author cheremin
 * @since 09.02.12,  20:17
 */
public class NIOHelper {

    public static final int DEFAULT_BUFFER_SIZE = 1 << 25;//=32Mb

    private final ByteBuffer byteBuffer;

    public NIOHelper() {
        this( DEFAULT_BUFFER_SIZE );
    }

    public NIOHelper( final int bufferSize ) {
        checkArgument( bufferSize > 0, "bufferSize(%s) must be > 0", bufferSize );

        byteBuffer = ByteBuffer.allocateDirect( bufferSize );
    }

    public void writeDataNIO( final FileChannel targetChannel,
                              final long targetOffset,
                              final IByteBufferProducer dataSource ) throws IOException {
        targetChannel.position( targetOffset );
        boolean hasMoreData = true;
        while ( hasMoreData ) {
            byteBuffer.clear();

            hasMoreData = dataSource.fillBuffer( byteBuffer );

            byteBuffer.rewind();

            targetChannel.write( byteBuffer );
        }
    }

    public void readDataNIO( final FileChannel sourceChannel,
                             final long sourceOffset,
                             final IByteBufferConsumer dataTarget ) throws IOException {
        sourceChannel.position( sourceOffset );
        boolean readMore = true;
        byteBuffer.clear();
        dataTarget.setupBuffer( byteBuffer );
        while ( readMore ) {
            final int bytesRead = sourceChannel.read( byteBuffer );

            byteBuffer.rewind();
            byteBuffer.limit( bytesRead );

            readMore = dataTarget.readBuffer( byteBuffer );
        }
    }

    @Override
    public String toString() {
        return String.format(
                "%s[buffer %s]",
                getClass().getSimpleName(),
                Formats.humanizedSize( byteBuffer.capacity() )
        );
    }

    public interface IByteBufferConsumer {
        public void setupBuffer( final ByteBuffer buffer ) throws IOException;

        /**
         * After method call, supplied buffer supplied should be prepared for reading. E.g.
         * {@link ByteBuffer#limit()} set up, and so on
         *
         * @return false, if it is enough data for this processor, true, if more data required
         */
        public boolean readBuffer( final ByteBuffer buffer ) throws IOException;
    }

    public interface IByteBufferProducer {


        /**
         * After method call, supplied buffer supplied should be prepared for writing. E.g.
         * filled up with data, {@link ByteBuffer#limit()} set up, and so on
         *
         * @return false, if processor finish it's write, true, if more data required
         */
        public boolean fillBuffer( final ByteBuffer buffer ) throws IOException;
    }
}

